/*
 *
 *
 */

#include "user_password.h"

#if defined(GKT_CONFIG_KEYPAD_ENABLE) && GKT_CONFIG_KEYPAD_ENABLE

__RETMEM_USR user_type_info_s g_user_password_info;

#if GKT_USER_PASSWORD_DYNAMIC_ENABLE
static int user_password_init(void)
{
	gkt_trace("user_password: init ...\n");
	user_dynpwd_init();
	return GKT_SUCCESS;
}
static int user_password_add_item(uint32_t item_index)
{
	/* before calling this API, the auth_user_used_nums 
		& user_used_nums has been increased by one */
	user_dynpwd_add_info(item_index);
	return GKT_SUCCESS;
}
static void user_password_delete_item(uint32_t item_index)
{
	user_dynpwd_delete_info(item_index);
	user_dynpwd_delete_item(item_index);
	/* after calling this API, the auth_user_used_nums 
		& user_used_nums has been decreased by one */
}
static void user_password_reset_info(void)
{
	user_dynpwd_reset_info();
}
static void user_password_reset_partition(void)
{
	user_dynpwd_reset_partition();
}
#else
#define user_password_init	NULL
#define user_password_add_item	NULL
#define user_password_delete_item	NULL
#define user_password_reset_info	NULL
#define user_password_reset_partition	NULL
#endif
#define user_password_config	NULL

static int user_password_compare(
				const uint8_t *verify_pwd_data, uint32_t verify_pwd_len,
				const uint8_t *exist_pwd_data, uint32_t exist_pwd_len)
{
	const uint8_t *data_off;
	uint32_t offset, k;

	offset = 0;
	while ((offset + exist_pwd_len) <= verify_pwd_len) {
		if (verify_pwd_data[offset] == exist_pwd_data[0]) {
			data_off = &verify_pwd_data[offset];
			for (k = 1; k < exist_pwd_len; k++) {
				if (exist_pwd_data[k] != data_off[k])
					break;
			}
			if (k == exist_pwd_len)
				return 1;
		}
		offset++;
	}

	return 0;
}

int gkt_user_password_is_exist(const uint8_t *data,    uint32_t length)
{ 
	const user_password_item_s *c_item; 
	uint32_t item_index, i; 

	item_index = 0; 
	while (item_index < g_user_password.instance.item_available_nums) {
		c_item = USER_PASSWORD_ITEM_C(item_index);  
		if (USER_ITEM_IS_USED(&c_item->header)   
			&& ((uint32_t)c_item->data_length == length))   
		{   
			for (i = 0; i < length; i++) {    
				if (c_item->data[i] != data[i])     
					break;   
			} 
			
			if (i == length)    
				return 1;  
		}  
		item_index++; 
	} 
	return 0;
}

static int user_password_verify(uint32_t *user_id, 
				uint32_t excluded_user_index, uint32_t authority, 
				const uint8_t *data, uint32_t length)
{
	const user_password_item_s *c_item;
	const user_password_item_s *c_candidate_item;
	uint32_t item_index;
	int retval, matched;

	if ((length < GKT_USER_PASSWORD_MINSIZE)
		|| (length > GKT_USER_PASSWORD_SCRAMBLE_MAXSIZE))
	{
		return GKT_EPARAM;
	}

	c_candidate_item = NULL;
	item_index = 0;
	if (authority >= GKT_USER_AUTH_MAX) {
		while (item_index < g_user_password.instance.item_available_nums) {
			c_item = USER_PASSWORD_ITEM_C(item_index);
			if (USER_ITEM_IS_USED(&c_item->header)
				&& ((uint32_t)c_item->data_length <= length)) 
			{
				matched = user_password_compare(data, length, 
								c_item->data, c_item->data_length);
				if (matched) {
					if ((uint32_t)c_item->header.user_index != excluded_user_index) {
						if ((uint32_t)c_item->data_length == length) {
							c_candidate_item = c_item;
							break;
						}
						else if (!c_candidate_item 
							|| ((uint32_t)c_candidate_item->header.user_index == excluded_user_index)
							|| (c_candidate_item->data_length < c_item->data_length))
						{
							c_candidate_item = c_item;
						}
					}
					else if (!c_candidate_item)
						c_candidate_item = c_item;
				}
			}

			item_index++;
		}

		if (c_candidate_item) {
			*user_id = GKT_USER_ID_BUILD(c_candidate_item->header.authority.type,
							c_candidate_item->header.authority.threaten,
							GKT_USER_TYPE_PASSWORD, 
							c_candidate_item->header.user_index);
			return GKT_USER_VERIFY_PASSED;
		}

#if GKT_USER_PASSWORD_DYNAMIC_ENABLE
		if (g_user_password_info.auth_user_used_nums[GKT_USER_AUTH_ADMIN] > 0)
			return user_dynpwd_verify(user_id, data, length);
#endif
	}
	else if (authority < GKT_USER_AUTH_TEMPORARY) {
		while (item_index < g_user_password.instance.item_available_nums) {
			c_item = USER_PASSWORD_ITEM_C(item_index);
			if (USER_ITEM_IS_USED(&c_item->header)
				&& ((uint32_t)c_item->header.authority.type == authority)
				&& ((uint32_t)c_item->data_length <= length)) 
			{
				matched = user_password_compare(data, length, 
								c_item->data, c_item->data_length);
				if (matched) {
					if ((uint32_t)c_item->header.user_index != excluded_user_index) {
						if ((uint32_t)c_item->data_length == length) {
							c_candidate_item = c_item;
							break;
						}
						else if (!c_candidate_item 
							|| ((uint32_t)c_candidate_item->header.user_index == excluded_user_index)
							|| (c_candidate_item->data_length < c_item->data_length))
						{
							c_candidate_item = c_item;
						}
					}
					else if (!c_candidate_item)
						c_candidate_item = c_item;
				}
			}

			item_index++;
		}

		if (c_candidate_item) {
			*user_id = GKT_USER_ID_BUILD(c_candidate_item->header.authority.type,
							c_candidate_item->header.authority.threaten,
							GKT_USER_TYPE_PASSWORD, 
							c_candidate_item->header.user_index);
			return GKT_USER_VERIFY_PASSED;
		}
	}

	gkt_unused(retval);
	
	return GKT_USER_VERIFY_FAILED;
}
#define user_password_verify_update	NULL
#define user_password_verify_finalize	NULL

#if USER_PASSWORD_ENROLL_SIMPLE_DISABLE
static int user_password_is_simple(
				const uint8_t *data, uint32_t length)
{
	uint32_t i;
	uint8_t diff, diff_tmp;

	diff = data[1] - data[0];
	for (i = 2; i < length; i++) {
		diff_tmp = data[i] - data[i - 1];
		if (diff_tmp != diff)
			return 0;
	}

	gkt_trace("user_password: TOO SIMPLE\n");
	return 1;
}
#endif

enum {
	USER_PASSWORD_SIMILARITY_SAME = 0,
	USER_PASSWORD_SIMILARITY_NORMAL,
	USER_PASSWORD_SIMILARITY_MAX
};

#if USER_PASSWORD_ENROLL_SIMILAR_DISABLE
static int user_password_check_similarity(
				const uint8_t *data, uint32_t length)
{
	const user_password_item_s *c_item;
	uint32_t item_index, offset, i;

	item_index = 0;
	do {
		c_item = USER_PASSWORD_ITEM_C(item_index);
		if (USER_ITEM_IS_USED(&c_item->header)) {
			offset = 0;
			if (length < c_item->data_length) {
				while ((offset + length) <= c_item->data_length) {
					if (c_item->data[offset] == data[0]) {
						for (i = 1; i < length; i++) {
							if (c_item->data[offset + i] != data[i])
								break;
						}
						if (i == length)
							return USER_PASSWORD_SIMILARITY_NORMAL;
					}
					offset++;
				}
			}
			else if (length == c_item->data_length) {
				for (offset = 0; offset < length; offset++) {
					if (c_item->data[offset] != data[offset])
						break;
				}
				if (offset == length)
					return USER_PASSWORD_SIMILARITY_SAME;
			}
			else {
				while ((offset + c_item->data_length) <= length) {
					if (c_item->data[0] == data[offset]) {
						for (i = 1; i < c_item->data_length; i++) {
							if (c_item->data[i] != data[offset + i])
								break;
						}
						if (i == c_item->data_length)
							return USER_PASSWORD_SIMILARITY_NORMAL;
					}
					offset++;
				}
			}
		}
	} while (++item_index < g_user_password.instance.item_available_nums);

	return USER_PASSWORD_SIMILARITY_MAX;
}
#elif USER_PASSWORD_ENROLL_SAME_DISABLE
static int user_password_check_similarity(
				const uint8_t *data, uint32_t length)
{
	const user_password_item_s *c_item;
	uint32_t item_index, offset;

	item_index = 0;
	do {
		c_item = USER_PASSWORD_ITEM_C(item_index);
		if (USER_ITEM_IS_USED(&c_item->header)
			&& (c_item->data_length == length)) 
		{
			for (offset = 0; offset < length; offset++) {
				if (c_item->data[offset] != data[offset])
					break;
			}

			if (offset == length)
				return USER_PASSWORD_SIMILARITY_SAME;
		}
	} while (++item_index < g_user_password.instance.item_available_nums);

	return USER_PASSWORD_SIMILARITY_MAX;
}
#else
#define user_password_check_similarity(data, length)	\
	USER_PASSWORD_SIMILARITY_MAX
#endif

static int user_password_enroll_run(const uint8_t *data, 
				uint32_t length)
{
	uint32_t i;
	int retval;

	gkt_unused(retval);

	if ((length >= GKT_USER_PASSWORD_MINSIZE)
		&& (length <= GKT_USER_PASSWORD_MAXSIZE)) 
	{
#if USER_PASSWORD_ENROLL_SIMPLE_DISABLE
		retval = user_password_is_simple(data, length);
		if (retval)
			return GKT_USER_ENROLL_UNAVAILABLE;
#endif

		retval = user_password_check_similarity(data, length);
		if (retval < USER_PASSWORD_SIMILARITY_MAX) {
			return (USER_PASSWORD_SIMILARITY_SAME == retval) ? 
						GKT_USER_ENROLL_EXIST :
						GKT_USER_ENROLL_UNAVAILABLE;
		}

		for (i = 0; i < length; i++)
			g_user_password.enroll_item.data[i] = data[i];
		while (i < GKT_USER_PASSWORD_MAXSIZE)
			g_user_password.enroll_item.data[i++] = 0xff;
		g_user_password.enroll_item.data_length = (uint8_t)length;

		return GKT_USER_ENROLL_COMPLETE;
	}

	return GKT_USER_ENROLL_OVERMUCH;
}

#define user_password_enroll_init	NULL
#define user_password_enroll_uninit	NULL

USER_TYPE_OPS_DEFINE(password);
user_password_s g_user_password = {
	.instance = {
		.type = GKT_USER_TYPE_PASSWORD,
		.flags = 0,
		.c_ops = USER_TYPE_OPS_PTR(password),
		.c_partition_name = "UserPassword",

		.info = &g_user_password_info,

		.auth_user_maxnums = {
			GKT_USER_PASSWORD_ADMIN_MAXNUMS,
			GKT_USER_PASSWORD_NORMAL_MAXNUMS,
			GKT_USER_PASSWORD_GUEST_MAXNUMS,
		},

		.item_size = USER_PASSWORD_ITEM_SIZE,
		.item_data_maxsize = 0,
		.item_header = &g_user_password.enroll_item.header,
	}
};

#endif

